-
-
Notifications
You must be signed in to change notification settings - Fork 19
feat(starlark): Add complete Starlark widget integration with auto-sc… #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…aling Implements a complete Starlark Apps plugin system that seamlessly imports widgets from the Tronbyte repository with zero modification required. The system provides automatic scaling for any display size and full management capabilities through a web UI. Features: - Pixlet renderer integration with multi-architecture binary support - WebP frame extraction with animation timing preservation - Zero-modification widget compatibility with Tronbyte apps - Automatic magnification calculation based on display dimensions - Multiple scaling strategies: Pixlet magnification, post-render scaling, centering - Repository browser with 500+ Tronbyte apps available - 13 REST API endpoints for complete lifecycle management - Full web UI with upload, configure, browse, and install capabilities - Configuration schema validation with JSON Schema - Intelligent caching with configurable TTL - Quality scoring system for magnification recommendations Plugin Components: - manager.py: Core plugin with auto-scaling logic and render pipeline - pixlet_renderer.py: Pixlet CLI wrapper with binary auto-detection - frame_extractor.py: WebP animation frame extraction and centering - tronbyte_repository.py: GitHub API integration with metadata parsing - config_schema.json: Complete configuration schema (magnify 0-8, scaling options) Web Interface: - Starlark Apps management UI with status banner - Modal dialogs for upload and repository browsing - Real-time app management (enable/disable, configure, uninstall) - Search and filter capabilities for repository apps - Display-aware magnification recommendations Auto-Scaling System: - Calculates optimal magnify based on display dimensions - Auto mode (magnify=0) as default with manual override - Quality scoring for each magnification level - Supports displays from 64x32 to 384x192+ - Prevents overflow while maximizing quality API Endpoints: - GET /api/v3/starlark/status - Plugin status with display info - GET /api/v3/starlark/apps - List installed apps - POST /api/v3/starlark/upload - Upload .star files - DELETE /api/v3/starlark/apps/<id> - Uninstall app - PUT /api/v3/starlark/apps/<id>/config - Update configuration - POST /api/v3/starlark/apps/<id>/toggle - Enable/disable app - POST /api/v3/starlark/apps/<id>/render - Force render - GET /api/v3/starlark/repository/browse - Browse Tronbyte repository - POST /api/v3/starlark/repository/install - Install from repository - GET /api/v3/starlark/repository/categories - Get app categories Documentation: - Complete project summary with all phases - Comprehensive scaling guide with performance considerations - Auto-scaling feature documentation with examples - Phase completion summaries with testing instructions Files Added: - plugin-repos/starlark-apps/ (plugin implementation) - scripts/download_pixlet.sh (binary download script) - starlark-apps/ (app storage directory) - starlark/ (comprehensive documentation) - web_interface/static/v3/js/starlark_apps.js (UI module) - web_interface/templates/v3/partials/starlark_apps.html (UI template) Files Modified: - .gitignore (added starlark paths) - web_interface/blueprints/api_v3.py (added 13 endpoints) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds a new Starlark Apps plugin and UI: app lifecycle and storage, Pixlet-based rendering (bundled discovery + download script), WebP frame extraction/optimization, Tronbyte repository client and install flows, many REST API endpoints and frontend UI, config schema and manifests, and extensive docs and guides. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WebUI as Web UI
participant API as API Handler
participant Manager as StarlarkAppsPlugin
participant Pixlet as PixletRenderer
participant Cache as Cache Manager
participant FrameExt as FrameExtractor
participant Repo as TronbyteRepository
participant Display as Display Manager
User->>WebUI: UI action (list / upload / install / render)
WebUI->>API: HTTP /api/v3/starlark/*
API->>Manager: handle request (list/install/render/config)
alt install from repository
API->>Repo: fetch metadata / download .star
Repo-->>API: .star file / metadata
API->>Manager: install_app(.star)
Manager->>Manager: create app dir, persist manifest
Manager-->>API: install result
end
alt render flow
Manager->>Cache: check cached WebP
alt cache hit
Cache-->>Manager: cached WebP path
else cache miss
Manager->>Pixlet: render .star -> WebP
Pixlet->>Pixlet: locate & run pixlet binary
Pixlet-->>Manager: rendered WebP output
Manager->>Cache: store rendered WebP
end
Manager->>FrameExt: load_webp(webp_path)
FrameExt-->>Manager: frames + durations
Manager->>FrameExt: scale/center/optimize frames
FrameExt-->>Manager: processed frames / GIF bytes
Manager->>Display: hand off frames for playback
end
API-->>WebUI: JSON response
WebUI-->>User: UI update
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🤖 Fix all issues with AI agents
In `@plugin-repos/starlark-apps/frame_extractor.py`:
- Around line 226-232: The current frame-sampling block can divide by zero when
max_frames == 0; update the guard so you only compute step and sample when
max_frames is a positive integer (e.g., check max_frames > 0 or max_frames is
not None and max_frames > 0) before using it with optimized, and keep the same
behavior otherwise (no reduction). Ensure you still reference the same variables
(max_frames, optimized, step, indices) and update the conditional that wraps the
sampling logic so ZeroDivisionError cannot occur.
In `@plugin-repos/starlark-apps/manager.py`:
- Around line 531-537: Sanitize and harden use of app_id before creating paths:
validate or transform app_id into a safe slug (e.g., restrict to [a-z0-9_.-] or
replace invalid chars) then build app_dir = (self.apps_dir /
safe_app_id).resolve(); after mkdir and before copying, verify
app_dir.resolve().is_relative_to(self.apps_dir.resolve()) (or compare parents)
and raise/abort if it escapes the base; apply the same changes to the other copy
block that builds star_dest and any code handling the app folder (references:
app_dir and star_dest).
- Around line 272-279: The current branch returns any user-provided magnify from
self.config.get("magnify") without bounds; change the logic that reads
config_magnify so you validate and clamp it to a safe range (e.g., min 1, max 8)
before returning — if config_magnify is missing, non-numeric, or out of range,
fall back to returning self.calculated_magnify; update the block around
self.config.get("magnify"), config_magnify and the return paths to enforce this
clamp and fallback.
In `@plugin-repos/starlark-apps/manifest.json`:
- Around line 7-8: Add a validate_config(self) -> bool method to the
StarlarkAppsPlugin class in manager.py so the plugin implements the required
configuration validation hook; the method should include a docstring ("Validate
plugin configuration."), perform whatever plugin-specific checks are needed
(placeholder logic is acceptable), and return True on success (or False on
invalid config).
In `@plugin-repos/starlark-apps/pixlet_renderer.py`:
- Around line 235-242: The subprocess.run calls that use
cwd=os.path.dirname(star_file) can raise FileNotFoundError when star_file is a
relative filename like "app.star"; compute a safe working directory instead
(e.g., resolved_parent = os.path.dirname(os.path.abspath(star_file)) or fallback
to None) and pass that as cwd for both the rendering subprocess.run (the call
that assigns result) and the schema-extraction subprocess.run (the later call
around lines 289-295) so cwd is never an empty string.
- Around line 68-84: Replace broad "except Exception" handlers with specific
exceptions and switch to logger.exception where stack traces are helpful: in the
try block that calls os.chmod (inside the method that checks bundled_path) catch
OSError instead of Exception and use logger.exception(...) for failures; in
_get_bundled_binary_path() replace its generic except with OSError; for all
subprocess call sites referenced in the review (the subprocess blocks around the
ranges handling TimeoutExpired) handle subprocess.TimeoutExpired first, then
catch (subprocess.SubprocessError, OSError) for other failures and log with
logger.exception(...) to preserve stack traces and context; update the named
functions/methods (_get_bundled_binary_path and the subprocess-invoking
functions/methods) accordingly.
In `@plugin-repos/starlark-apps/tronbyte_repository.py`:
- Around line 200-201: The current truthy check "if max_apps:" ignores zero;
change it to an explicit None check so a zero value correctly returns an empty
list — replace the condition guarding the slice of apps (the "if max_apps:" that
sets "apps = apps[:max_apps]") with "if max_apps is not None:" so slicing by 0
yields [] while None keeps the full list.
- Around line 165-170: The code calls yaml.safe_load into the variable metadata
and assumes it's a dict by assigning metadata['id'] = app_id, which raises
TypeError for None or non-mapping YAML; modify the try block around
yaml.safe_load to validate that metadata is an instance of dict (e.g.,
isinstance(metadata, dict)) before mutating it and if not, log or skip indexing
that manifest (or initialize an empty dict if appropriate), and/or expand the
except block to catch TypeError along with yaml.YAMLError so non-mapping results
from yaml.safe_load (metadata) are handled gracefully rather than propagating an
exception.
In `@scripts/download_pixlet.sh`:
- Around line 71-83: The tar extraction block currently can be aborted by set
-e; change it to handle failures explicitly by running tar -xzf "$temp_file" -C
"$temp_dir" and checking its exit status (e.g., capture $? or use a conditional)
so that on failure you log an error including "$temp_file", clean up
"$temp_dir", and return a non-zero status instead of letting set -e terminate
the whole script; then proceed to locate "$extracted_binary" as before. Ensure
the checks reference temp_file, temp_dir, tar, and extracted_binary so the
failure path is handled per-architecture and reports cleanly.
In `@starlark-apps/README.md`:
- Around line 9-22: Update the fenced code block in README.md that shows the
starlark-apps folder listing to include a language identifier (e.g., change the
opening ``` to ```text) so markdownlint MD040 is satisfied; locate the
multi-line block beginning with "starlark-apps/" and replace the opening fence
accordingly.
In `@starlark/AUTO_SCALING_FEATURE.md`:
- Around line 11-29: Update fenced code blocks to include language tags (e.g.,
use ```python for the _calculate_optimal_magnify code block and ```text for
display examples) so MD040 is resolved; replace emphasized "heading-like" lines
(e.g., lines like **Example: 192x64 display**) with proper Markdown headings
(e.g., ### Example: 192x64 display) to fix MD036; and normalize the phrase
"power users" to the hyphenated "power-users" where appropriate. Apply the same
fixes consistently in the other affected sections noted (around the other ranges
referenced).
In `@starlark/PHASE2_COMPLETE.md`:
- Around line 9-25: The header states "10 new REST API endpoints" but the listed
routes show 9; update PHASE2_COMPLETE.md so the count matches the list by either
changing the header to "9 new REST API endpoints" or adding the missing route to
the list (verify which is intended), and ensure the canonical identifiers (e.g.,
GET /api/v3/starlark/status, GET /api/v3/starlark/apps, GET
/api/v3/starlark/apps/<app_id>, POST /api/v3/starlark/upload, DELETE
/api/v3/starlark/apps/<app_id>, POST /api/v3/starlark/apps/<app_id>/toggle, POST
/api/v3/starlark/apps/<app_id>/render, GET
/api/v3/starlark/apps/<app_id>/config, PUT
/api/v3/starlark/apps/<app_id>/config) are consistent with the chosen count.
In `@web_interface/blueprints/api_v3.py`:
- Around line 6132-6156: The upload handler currently accepts app_id,
render_interval and display_duration without validation (variables app_id,
render_interval, display_duration and call to starlark_plugin.install_app),
enabling path traversal and invalid timing; fix by: 1) normalize and validate
app_id (including when derived from file.filename) to a safe slug (e.g.
lowercase, allow only a-z0-9 and underscore, reject path separators or “..”, and
fallback to a validated generated slug) and return a BadRequest on failure; 2)
enforce bounds on render_interval and display_duration (e.g. min 1, sane max
like 86400) and coerce/type-check using request.form.get with type=int,
rejecting out-of-range values; and 3) apply the same validation logic in the
repository install flow (the other install path referenced in the comment)
before calling starlark_plugin.install_app so no unsafe app_id or timing values
reach disk/write operations.
In `@web_interface/static/v3/js/starlark_apps.js`:
- Around line 114-155: The code injects unescaped backend data via innerHTML and
inline onclicks (see innerHTML usage, magnifyHint, data.pixlet_version and
displayInfo.display_size) creating XSS risks; add a module-level
sanitizeHtml(str) utility that escapes HTML special chars and use it to sanitize
every dynamic interpolation (e.g., data.pixlet_version, displaySize, magnifyRec
display text, app.name, app.id, app.summary, app.author, app.category,
field.name/description/options, and config[key]) before composing innerHTML, and
for clickable app controls replace inline onclick handlers (e.g.,
toggleStarlarkApp(...) attributes) with data-* attributes and attach event
listeners via addEventListener that read sanitized dataset values to avoid
attribute injection.
- Around line 68-76: The drag-and-drop handler currently tries to assign a
FileList directly via fileInput.files = files inside the dropZone 'drop'
listener, which fails across browsers; instead create a DataTransfer, add the
dropped File objects to it, set fileInput.files = dataTransfer.files, then call
handleFileSelect({ target: fileInput }); update the drop event handler in the
dropZone listener (where fileInput and handleFileSelect are referenced) to build
and use a DataTransfer object and ensure e.preventDefault() is preserved.
In `@web_interface/templates/v3/partials/starlark_apps.html`:
- Around line 27-50: The partial duplicates the outer wrapper id
"starlark-apps-section", causing getElementById/CSS conflicts; rename the inner
container id in web_interface/templates/v3/partials/starlark_apps.html (the div
currently id="starlark-apps-section") to a unique id (e.g.
"starlark-apps-inner") and update all references in your frontend code (search
and replace in starlark_apps.js for selectors like "#starlark-apps-section" and
any document.getElementById/use of that id), plus any CSS or tests that target
that id so they point to the new identifier.
🧹 Nitpick comments (12)
scripts/download_pixlet.sh (1)
117-120: Avoidls | grepwhen listing binaries.This is fragile with non-standard filenames. Prefer a glob-based listing.
♻️ Proposed refactor
-echo "Installed binaries:" -ls -lh "$BIN_DIR" | grep -v "^total" || echo "No binaries found" +echo "Installed binaries:" +shopt -s nullglob +files=("$BIN_DIR"/*) +if ((${`#files`[@]})); then + ls -lh "${files[@]}" +else + echo "No binaries found" +fistarlark/SCALING_GUIDE.md (1)
17-22: Consider documenting auto-scaling mode (magnify=0).The PR summary mentions that
magnify=0enables auto-scaling mode which "calculates optimal magnification for display dimensions." However, this guide only documents explicit magnify values (1-4). Consider adding documentation for the auto mode:{ "magnify": 0 // Auto-detect optimal magnification based on display size }This would help users understand when to use auto vs. explicit magnification.
starlark/starlarkplan.md (1)
1-2: Minor: Remove remnant title.Line 1 contains "Starlark plan.txt" which appears to be a leftover filename that should be removed since the actual title is on line 2.
-Starlark plan.txt # Plan: Tidbyt/Tronbyt .star App Integration for LEDMatrixplugin-repos/starlark-apps/frame_extractor.py (3)
47-102: Consider using context manager to ensure file handle cleanup.
Image.open()returns a file-like object that holds a file handle open. While PIL often handles this gracefully, explicitly closing the image or using a context manager ensures proper resource cleanup, which is important for Raspberry Pi's limited resources.♻️ Suggested improvement
def load_webp(self, webp_path: str) -> Tuple[bool, Optional[List[Tuple[Image.Image, int]]], Optional[str]]: ... try: - img = Image.open(webp_path) - - # Check if animated - is_animated = getattr(img, "is_animated", False) - - if not is_animated: - # Static image - single frame - logger.debug(f"Loaded static WebP: {webp_path}") - return True, [(img.copy(), self.default_frame_delay)], None - - # Animated WebP - extract all frames - frames = [] - frame_count = getattr(img, "n_frames", 1) - ... + with Image.open(webp_path) as img: + # Check if animated + is_animated = getattr(img, "is_animated", False) + + if not is_animated: + # Static image - single frame + logger.debug(f"Loaded static WebP: {webp_path}") + return True, [(img.copy(), self.default_frame_delay)], None + + # Animated WebP - extract all frames + frames = [] + frame_count = getattr(img, "n_frames", 1) + ...As per coding guidelines, cleaning up resources regularly is important for Raspberry Pi's limited RAM.
95-102: Uselogger.exception()for error logging with stack traces.When logging errors in exception handlers,
logger.exception()automatically includes the stack trace, which aids remote debugging on Raspberry Pi.♻️ Suggested fix
except FileNotFoundError: error = f"WebP file not found: {webp_path}" - logger.error(error) + logger.exception(error) return False, None, error except Exception as e: error = f"Error loading WebP: {e}" - logger.error(error) + logger.exception(error) return False, None, errorAs per coding guidelines, comprehensive logging is needed for remote debugging on Raspberry Pi.
280-284: Uselogger.exception()for GIF conversion errors.Similar to the earlier suggestion, using
logger.exception()here will capture the stack trace for debugging.except Exception as e: - logger.error(f"Error converting frames to GIF: {e}") + logger.exception(f"Error converting frames to GIF: {e}") return Noneweb_interface/templates/v3/partials/starlark_apps.html (1)
53-210: Add dialog semantics and accessible labels for modal controls.These modals lack
role="dialog",aria-modal, and an accessible label for icon-only close buttons. This hurts screen readers and keyboard navigation. Recommend adding ARIA attributes and button labels (apply to all three modals).♿ Example accessibility additions (repeat for other modals)
-<div id="upload-star-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center"> +<div id="upload-star-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center" + role="dialog" aria-modal="true" aria-labelledby="upload-star-title"> @@ - <div class="p-6"> + <div class="p-6"> <div class="flex items-center justify-between mb-4"> - <h3 class="text-xl font-bold text-gray-900">Upload Starlark App</h3> - <button onclick="closeUploadModal()" class="text-gray-500 hover:text-gray-700"> + <h3 id="upload-star-title" class="text-xl font-bold text-gray-900">Upload Starlark App</h3> + <button onclick="closeUploadModal()" class="text-gray-500 hover:text-gray-700" aria-label="Close upload modal"> <i class="fas fa-times text-xl"></i> </button> </div>plugin-repos/starlark-apps/config_schema.json (1)
76-96: Disallow unknown keys insidetransitionto keep validation strict.The root schema blocks extra properties, but
transitionallows any unknown fields. Tighten this to avoid silent typos.♻️ Proposed schema tightening
"transition": { "type": "object", "description": "Transition settings for app display", "properties": { @@ "enabled": { "type": "boolean", "default": true } - } + }, + "additionalProperties": false }plugin-repos/starlark-apps/tronbyte_repository.py (1)
81-89: Prefer specific exceptions and stack traces in logs.
BareExceptionhides root causes andlogger.errordrops stack traces. Catch specific exceptions (e.g.,ValueError,requests.RequestException,OSError) and uselogger.exceptionwith a consistent context prefix for troubleshooting. As per coding guidelines, use structured logging and avoid bare exceptions.♻️ Example pattern (apply similarly in the other blocks)
- except requests.Timeout: - logger.error(f"Request timeout: {url}") + except requests.Timeout: + logger.exception("[Starlark Repo] Request timeout: %s", url) return None - except requests.RequestException as e: - logger.error(f"Request error: {e}") + except requests.RequestException: + logger.exception("[Starlark Repo] Request error: %s", url) return None - except Exception as e: - logger.error(f"Unexpected error: {e}") + except ValueError: + logger.exception("[Starlark Repo] Invalid JSON response: %s", url) return NoneAlso applies to: 112-114, 251-252
web_interface/blueprints/api_v3.py (1)
6260-6274: Validate Starlark app config against schema before saving.
app.config.update(data)persists any keys/types and immediately re-renders. If the payload doesn’t matchapp.schema, the app can be left in a broken state. Consider validating (and rejecting) or filtering againstapp.schemabefore saving and rendering.plugin-repos/starlark-apps/manager.py (2)
164-165: Prefer display_manager.width/height for size queries.The plugin currently reads
display_manager.matrix.*. Usingdisplay_manager.width/height(with fallback) keeps compatibility with other display managers and aligns with project guidance.Based on learnings, use display_manager.width/height to support different display sizes.🔧 Suggested access pattern
- display_width = self.display_manager.matrix.width - display_height = self.display_manager.matrix.height + display_width = getattr(self.display_manager, "width", self.display_manager.matrix.width) + display_height = getattr(self.display_manager, "height", self.display_manager.matrix.height)- width = self.display_manager.matrix.width - height = self.display_manager.matrix.height + width = getattr(self.display_manager, "width", self.display_manager.matrix.width) + height = getattr(self.display_manager, "height", self.display_manager.matrix.height)Also applies to: 462-464
412-420: Consider using cache file mtime for TTL across restarts.
app.last_render_timeresets on restart, so cached renders are ignored even if still fresh. Basing TTL oncache_filemtime keeps the cache effective across restarts.🧹 Optional TTL tweak
- if (not force and use_cache and app.cache_file.exists() and - (current_time - app.last_render_time) < cache_ttl): - # Use cached render - self.logger.debug(f"Using cached render for: {app.app_id}") - return self._load_frames_from_cache(app) + if not force and use_cache and app.cache_file.exists(): + cache_age = current_time - app.cache_file.stat().st_mtime + if cache_age < cache_ttl: + # Use cached render + self.logger.debug(f"Using cached render for: {app.app_id}") + return self._load_frames_from_cache(app)
| <!-- Installed Apps Section --> | ||
| <div id="starlark-apps-section"> | ||
| <div class="flex items-center justify-between mb-5 pb-3 border-b border-gray-200"> | ||
| <div class="flex items-center gap-3"> | ||
| <h3 class="text-lg font-bold text-gray-900">Installed Apps</h3> | ||
| <span id="starlark-apps-count" class="text-sm text-gray-500 font-medium">0 apps</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Apps Grid --> | ||
| <div id="starlark-apps-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6"> | ||
| <!-- Apps will be loaded here --> | ||
| </div> | ||
|
|
||
| <!-- Empty State --> | ||
| <div id="starlark-apps-empty" class="hidden text-center py-12"> | ||
| <i class="fas fa-star text-6xl text-gray-300 mb-4"></i> | ||
| <h3 class="text-lg font-semibold text-gray-700 mb-2">No Starlark Apps Installed</h3> | ||
| <p class="text-gray-500 mb-4">Upload a .star file to get started</p> | ||
| <button onclick="document.getElementById('upload-star-btn').click()" class="btn bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md"> | ||
| <i class="fas fa-upload mr-2"></i>Upload Your First App | ||
| </button> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "starlark-apps-section" -C 3Repository: ChuckBuilds/LEDMatrix
Length of output: 1220
🏁 Script executed:
# Search for getElementById or querySelector usage of starlark-apps-section
rg "starlark-apps-section" --type html --type js --type ts -C 2Repository: ChuckBuilds/LEDMatrix
Length of output: 554
🏁 Script executed:
# Check the file being reviewed to understand the full context
wc -l web_interface/templates/v3/partials/starlark_apps.htmlRepository: ChuckBuilds/LEDMatrix
Length of output: 123
🏁 Script executed:
# Read the full file to understand structure
cat -n web_interface/templates/v3/partials/starlark_apps.htmlRepository: ChuckBuilds/LEDMatrix
Length of output: 13100
Fix duplicate starlark-apps-section ID in the DOM.
The outer wrapper in index.html (per PHASE2_COMPLETE.md line 267) uses id="starlark-apps-section", and this partial repeats the same ID at line 28. This creates duplicate IDs when the partial is included, breaking getElementById() and CSS selectors. Rename the inner container and update any JS selectors referencing it.
Suggested fix
- <div id="starlark-apps-section">
+ <div id="starlark-apps-installed-section">Update JS selectors in starlark_apps.js to use the new ID.
🤖 Prompt for AI Agents
In `@web_interface/templates/v3/partials/starlark_apps.html` around lines 27 - 50,
The partial duplicates the outer wrapper id "starlark-apps-section", causing
getElementById/CSS conflicts; rename the inner container id in
web_interface/templates/v3/partials/starlark_apps.html (the div currently
id="starlark-apps-section") to a unique id (e.g. "starlark-apps-inner") and
update all references in your frontend code (search and replace in
starlark_apps.js for selectors like "#starlark-apps-section" and any
document.getElementById/use of that id), plus any CSS or tests that target that
id so they point to the new identifier.
- Add input validation and sanitization for app_id in upload/repository install - Prevent path traversal attacks by validating app_id format (a-z0-9_ only) - Add bounds checking for render_interval and display_duration (1-86400) - Fix division by zero in frame_extractor optimize_frames when max_frames=0 - Add magnify validation and clamping (1-8) with fallback to calculated value - Sanitize app_id paths with verification to prevent directory traversal - Replace broad Exception handlers with specific exceptions (OSError, SubprocessError) - Use logger.exception for better error diagnostics - Fix safe working directory calculation for subprocess calls - Fix tar extraction error handling in download_pixlet.sh - Fix YAML parsing to handle non-dict results gracefully - Fix max_apps truthy check to handle zero correctly - Update markdown documentation (MD040, MD036 compliance) - Fix endpoint count discrepancy in PHASE2_COMPLETE.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@plugin-repos/starlark-apps/frame_extractor.py`:
- Around line 34-103: The load_webp function opens images with
Image.open(webp_path) but never closes the file handle; update load_webp to use
a context manager (with Image.open(webp_path) as img:) so the file is closed
automatically, or ensure img.close() is called in a finally block; modify the
code paths inside load_webp (including the animated branch that seeks frames,
early returns when not animated, and error/exception returns) to rely on the
with-context or explicitly close img before returning so you avoid file
descriptor leaks on long-running systems (references: function load_webp, local
variable img, attribute default_frame_delay).
In `@plugin-repos/starlark-apps/manager.py`:
- Around line 580-619: The _load_frames_from_cache method currently loads all
frames without limiting them; after extracting and before assigning to
app.frames call FrameExtractor.optimize_frames(frames, max_frames) using a new
config key "max_frames" (fallback to None or a safe default like 0/None to mean
no limit) so frames are truncated when needed; update the code in
_load_frames_from_cache to read max_frames from self.config (e.g.,
self.config.get("max_frames")) and pass it to
self.extractor.optimize_frames(...) (or self.extractor.optimize_frames(frames,
max_frames)) before setting app.frames, ensuring memory is limited on Raspberry
Pi while preserving existing center/scale logic.
In `@plugin-repos/starlark-apps/tronbyte_repository.py`:
- Around line 91-114: In _fetch_raw_file replace the direct call to requests.get
with using the repository's configured session (self.session.get) so auth
headers and rate-limit benefits are applied; keep the same URL, timeout=10, and
existing response.status_code check/return handling and exception logging
(logger.error) but call self.session.get(url, timeout=10) instead of
requests.get; ensure no other references to requests.get remain in the
_fetch_raw_file method.
In `@starlark/AUTO_SCALING_FEATURE.md`:
- Around line 31-38: Update the example in AUTO_SCALING_FEATURE.md to make the
magnify value for the `128x32` example consistent with the algorithm/table
(min(width_scale=2,height_scale=1)=1): change the `128x32` line that currently
shows `magnify=2` to `magnify=1` so the examples match the table and algorithm.
In `@web_interface/blueprints/api_v3.py`:
- Around line 6285-6290: Replace the bare except around os.unlink(temp_path)
with a specific OSError handler that logs the error instead of silently
swallowing it: catch OSError (or OSError as e) and call the existing logger (or
processLogger) to record a descriptive message including temp_path and the
exception; do the same replacement in the repository install cleanup block where
os.unlink is similarly wrapped so permission/race issues are surfaced rather
than ignored.
- Around line 6357-6405: The update_starlark_app_config handler currently
accepts zero/negative timing values; before applying data to app.config in
update_starlark_app_config, validate any render_interval and display_duration
fields from request.get_json(): coerce to integers, enforce the same bounds used
at install (e.g., minimum 1 and a sensible maximum such as 86400 seconds),
reject or clamp values outside those bounds and return a 400 with an error
message if invalid; then proceed to call app.save_config() and
starlark_plugin._render_app(app, force=True) only when validated.
🧹 Nitpick comments (13)
scripts/download_pixlet.sh (5)
31-36: Windows architecture mapping is defined but never used.The
download_binaryfunction handles Windows binaries (lines 44-46), but theARCHITECTURESarray does not include any Windows entries. Either add Windows architecture or remove the dead Windows-handling code.♻️ Option A: Add Windows support
declare -A ARCHITECTURES=( ["linux-amd64"]="pixlet_Linux_x86_64.tar.gz" ["linux-arm64"]="pixlet_Linux_arm64.tar.gz" ["darwin-amd64"]="pixlet_Darwin_x86_64.tar.gz" ["darwin-arm64"]="pixlet_Darwin_arm64.tar.gz" + ["windows-amd64"]="pixlet_Windows_x86_64.zip" )Note: Windows archives are typically
.zip, not.tar.gz, which would require additional extraction logic.♻️ Option B: Remove unused Windows handling
- # Add .exe for Windows - if [[ "$arch" == *"windows"* ]]; then - binary_name="${binary_name}.exe" - fi
62-63: Declare and assign separately to avoid masking return values.If
mktemp -dfails, the error is masked by the local declaration. Split declaration and assignment for safer error handling underset -e.♻️ Proposed fix
- local temp_dir=$(mktemp -d) - local temp_file="$temp_dir/$archive_name" + local temp_dir + temp_dir=$(mktemp -d) + local temp_file="$temp_dir/$archive_name"
80-80: Declare and assign separately forextracted_binary.Same issue as above—if
findfails, the return value is masked.♻️ Proposed fix
- local extracted_binary=$(find "$temp_dir" -name "pixlet" -o -name "pixlet.exe" | head -n 1) + local extracted_binary + extracted_binary=$(find "$temp_dir" -name "pixlet" -o -name "pixlet.exe" | head -n 1)
100-101: Declaresizeseparately and handle potentialstatfailure gracefully.The chained
statcommand with||fallback works, but the local declaration still masks the overall exit status.♻️ Proposed fix
- local size=$(stat -f%z "$output_path" 2>/dev/null || stat -c%s "$output_path" 2>/dev/null) + local size + size=$(stat -f%z "$output_path" 2>/dev/null || stat -c%s "$output_path" 2>/dev/null || echo "unknown")
121-125: Avoidls | grepfor listing files.Parsing
lsoutput is fragile. Use a glob orfindinstead for robustness with unusual filenames.♻️ Proposed fix
echo "" echo "Installed binaries:" -ls -lh "$BIN_DIR" | grep -v "^total" || echo "No binaries found" +if compgen -G "$BIN_DIR/*" > /dev/null 2>&1; then + ls -lh "$BIN_DIR"/* +else + echo "No binaries found" +fiplugin-repos/starlark-apps/pixlet_renderer.py (2)
142-157: Consider adding logging for edge cases in_get_safe_working_directory.When the method returns
Nonedue to an exception or empty path, no logging occurs. This could make debugging difficult on Raspberry Pi deployments.♻️ Proposed enhancement
def _get_safe_working_directory(self, star_file: str) -> Optional[str]: """ Get a safe working directory for subprocess execution. Args: star_file: Path to .star file Returns: Resolved parent directory, or None if empty or invalid """ try: resolved_parent = os.path.dirname(os.path.abspath(star_file)) # Return None if empty string to avoid FileNotFoundError - return resolved_parent if resolved_parent else None + if not resolved_parent: + logger.debug(f"Empty parent directory for star_file: {star_file}") + return None + return resolved_parent except (OSError, ValueError): + logger.debug(f"Could not resolve working directory for: {star_file}") return None
320-331: Moveimport jsonto module level.While importing inside the function reduces initial load time,
jsonis a standard library module with negligible import cost. Module-level imports improve code clarity and are the conventional Python style.♻️ Proposed fix
At line 14, add json to imports:
from typing import Dict, Any, Optional, Tuple +import jsonThen at line 322:
- import json try: schema = json.loads(result.stdout)plugin-repos/starlark-apps/tronbyte_repository.py (2)
91-91: Use explicitOptional[str]type hint instead of implicitNonedefault.PEP 484 prohibits implicit
Optional. The type hint should explicitly indicate the parameter can beNone.♻️ Proposed fix
- def _fetch_raw_file(self, file_path: str, branch: str = None) -> Optional[str]: + def _fetch_raw_file(self, file_path: str, branch: Optional[str] = None) -> Optional[str]:
252-262: Catch specific exceptions for file operations instead of broadException.Per coding guidelines, catch specific exceptions. File writing can raise
OSError(includesIOError,PermissionError, etc.).♻️ Proposed fix
try: output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'w', encoding='utf-8') as f: f.write(content) logger.info(f"Downloaded {app_id}.star to {output_path}") return True, None - except Exception as e: - logger.error(f"Failed to save .star file: {e}") + except OSError as e: + logger.exception(f"Failed to save .star file: {e}") return False, f"Failed to save file: {e}"web_interface/blueprints/api_v3.py (1)
6515-6522: Clamp repository browse limit to a sane max.
A hugelimitcan amplify API latency and memory usage. Consider bounding to a reasonable range.♻️ Suggested change
- limit = request.args.get('limit', 50, type=int) + limit = request.args.get('limit', 50, type=int) + if limit is None: + limit = 50 + limit = max(1, min(limit, 200))plugin-repos/starlark-apps/manager.py (3)
76-84: Uselogger.exceptionfor better debugging on Raspberry Pi.When logging errors with exception context,
logger.exceptionautomatically includes the stack trace, which is valuable for remote debugging on Pi deployments. As per coding guidelines, comprehensive logging aids remote troubleshooting.♻️ Proposed fix
def save_config(self) -> bool: """Save current configuration to file.""" try: with open(self.config_file, 'w') as f: json.dump(self.config, f, indent=2) + return True except Exception as e: - logger.error(f"Could not save config for {self.app_id}: {e}") + logger.exception(f"Could not save config for {self.app_id}: {e}") return False - return True
305-311: Uselogger.exceptionfor full stack trace in error handler.♻️ Proposed fix
except Exception as e: - self.logger.error(f"Error getting magnify recommendation: {e}") + self.logger.exception(f"Error getting magnify recommendation: {e}") return { 'display_size': 'unknown', 'calculated_magnify': 1, 'recommendations': [] }
443-454: Uselogger.exceptionfor full stack traces in error handlers.Multiple error handlers here use
logger.errorbut would benefit fromlogger.exceptionfor better remote debugging on Raspberry Pi.♻️ Proposed fix
try: # Use safe_app_id for internal storage to match directory structure app = StarlarkApp(safe_app_id, app_dir, app_manifest) self.apps[safe_app_id] = app self.logger.debug(f"Loaded app: {app_id} (sanitized: {safe_app_id})") except Exception as e: - self.logger.error(f"Error loading app {app_id}: {e}") + self.logger.exception(f"Error loading app {app_id}: {e}") self.logger.info(f"Loaded {len(self.apps)} Starlark apps") except Exception as e: - self.logger.error(f"Error loading apps manifest: {e}") + self.logger.exception(f"Error loading apps manifest: {e}")
… improvements - Fix file handle leak in frame_extractor load_webp using context manager - Add frame optimization with max_frames config to limit memory usage - Add input validation and sanitization for app_id in upload/repository install - Prevent path traversal attacks by validating app_id format (a-z0-9_ only) - Add bounds checking for render_interval and display_duration (1-86400) - Add timing value validation in config update endpoint - Replace bare except handlers with specific OSError handlers and logging - Fix safe working directory calculation for subprocess calls - Use session.get instead of requests.get in repository for auth/rate limits - Fix duplicate ID in HTML template (starlark-apps-container -> starlark-apps-inner) - Improve XSS protection with consistent sanitization in JavaScript - Fix magnify example inconsistency in AUTO_SCALING_FEATURE.md - Fix endpoint count discrepancy in PHASE2_COMPLETE.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In `@plugin-repos/starlark-apps/frame_extractor.py`:
- Around line 34-101: The static-WebP branch in load_webp returns img.copy()
without converting to RGB, causing downstream paste/encoding failures; update
the static-image return path in load_webp to convert the image to RGB (e.g.,
img.convert("RGB")) and return a copy of that RGB image with
self.default_frame_delay so the returned frames list matches the animated
branch's (RGB, duration) tuples used by center_frames() and
frames_to_gif_data().
In `@plugin-repos/starlark-apps/manager.py`:
- Around line 90-101: The manifest-provided intervals in get_render_interval and
get_display_duration are not validated and can be missing, non-numeric, or
extreme; update these methods to coerce values to ints with try/except (falling
back to defaults 300 and 15), then clamp render_interval to a safe range (e.g.,
min 5, max 3600) and display_duration to a safe range (e.g., min 1, max 600),
returning the validated int; keep should_render using get_render_interval and
last_render_time unchanged so it benefits from the validation.
In `@plugin-repos/starlark-apps/tronbyte_repository.py`:
- Around line 116-146: The list_apps method currently treats an empty list as a
failure because it uses "if not data"; change that guard to explicitly check for
None (e.g., "if data is None") so an empty repo ([]) is accepted as a valid
empty list; keep the subsequent type check "if not isinstance(data, list)" and
the existing app-filtering logic in list_apps to return (True, [], None) when
the repo has no apps, referencing the list_apps method and the _make_request
call to locate where to change the condition.
In `@web_interface/blueprints/api_v3.py`:
- Around line 5981-5988: The endpoint calls
api_v3.plugin_manager.get_plugin('starlark-apps') without guarding for a nil
plugin_manager, which can raise and return a 500 during startup; add a nil-check
for api_v3.plugin_manager before calling get_plugin (same pattern used in other
endpoints), and return a clear JSON error like
{'status':'error','message':'Plugin manager not
initialized','pixlet_available':False} when plugin_manager is None; apply this
guard to all Starlark-related routes that reference api_v3.plugin_manager or
call get_plugin to prevent unhandled exceptions.
- Around line 6554-6561: The user-controlled "limit" pulled from request.args
must be validated and clamped before calling repo.list_apps_with_metadata;
update the code around where limit is read (the request.args.get('limit', 50,
type=int') assignment) to enforce an integer >= 1 and <= a sane MAX (e.g. 200),
then pass the clamped value to
repo.list_apps_with_metadata(max_apps=clamped_limit), and log the effective
limit via the existing logger.info call to reflect the clamp.
- Around line 6231-6249: The code currently uses request.form.get(..., type=int)
which coerces invalid strings to the provided default and hides bad user input;
change the calls that fetch render_interval_input and display_duration_input to
retrieve the raw value (no type=int) so you pass the raw string into
_validate_timing_value for strict validation, then only apply the numeric
defaults (300 for render_interval, 15 for display_duration) when the field is
missing (i.e., request.form.get returns None) or when _validate_timing_value
returns (None, None) semantics for missing — keep using the existing symbols
render_interval_input, display_duration_input, and _validate_timing_value to
locate and modify the logic.
In `@web_interface/static/v3/js/starlark_apps.js`:
- Around line 432-434: Remove the unnecessary sanitizeHtml() call when assigning
to the element's textContent: instead of using sanitizeHtml(app.name || ''),
assign app.name || '' directly to
document.getElementById('config-app-name').textContent so the browser treats it
as plain text (no HTML-escaping/double-escaping); update the line where
sanitizeHtml is used with element id 'config-app-name' and ensure app.name
fallback remains (app.name || '').
- Around line 290-317: The click handler is being attached repeatedly in
setupAppCardEventDelegation (and likewise in setupRepositoryAppEventDelegation)
whenever data reloads; add a guard so the listener is only attached once per
grid element (e.g. check/set a dataset flag or use a WeakSet keyed by the grid)
before calling grid.addEventListener, and bail out if the flag indicates the
listener is already attached; keep the existing logic inside the listener and
only change the attachment site in setupAppCardEventDelegation and
setupRepositoryAppEventDelegation.
- Around line 301-305: The switch case 'toggle' uses a lexical declaration const
enabled inside the case which can cause TDZ/scope issues; to fix, wrap the case
body in a block ({ ... }) so the const enabled and subsequent await
toggleStarlarkApp(appId, !enabled) are scoped locally to that case; update the
case 'toggle' branch around the existing statements (const enabled, await
toggleStarlarkApp, break) so the block encloses them and behavior remains the
same.
🧹 Nitpick comments (1)
web_interface/blueprints/api_v3.py (1)
6110-6138: Replacemd5withsha256for the fallback app_id slug.The repository's security linting rules (Ruff with Bandit enabled) will flag
md5usage. Swapping tosha256eliminates the policy noise without changing behavior since you already slice to[:12].🔁 Suggested change
- hash_slug = hashlib.md5(app_id.encode()).hexdigest()[:12] + hash_slug = hashlib.sha256(app_id.encode()).hexdigest()[:12]
- Add plugin_manager nil-checks to all Starlark endpoints to prevent 500 errors during startup - Fix empty list handling in tronbyte_repository.list_apps() to accept empty repos - Add validation and clamping for render_interval (5-3600s) and display_duration (1-600s) - Fix static WebP images to convert to RGB before returning (matches animated branch) - Replace md5 with sha256 for fallback app_id slug generation (security linting compliance)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@plugin-repos/starlark-apps/manager.py`:
- Around line 170-219: The config validator misses checking "max_frames", which
can be non-integer and later cause a runtime error when passed from
_load_frames_from_cache into optimize_frames; add validation in validate_config
to ensure "max_frames" (self.config["max_frames"]) is an integer >= 1 (or an
allowed upper bound if desired), log an error via self.logger.error when
invalid, and return False so invalid configs are rejected before
_load_frames_from_cache or optimize_frames are called.
In `@web_interface/blueprints/api_v3.py`:
- Around line 6254-6256: The uploaded filename may include client-side paths
like "C:\fakepath\..." which trips the path-separator check; update the filename
base extraction before calling _validate_and_sanitize_app_id by using the
pathlib Path stem to strip any directory and extension (i.e., derive
filename_base from Path(file.filename).stem when file.filename exists), ensuring
you still handle a missing file.filename by falling back to None and then pass
that filename_base into _validate_and_sanitize_app_id.
- Around line 6122-6167: _validate_and_sanitize_app_id currently replaces
hyphens with underscores which alters repo IDs like "foo-bar"; update the
sanitizer so hyphens are allowed and preserved for repo/path lookups by adding
'-' to the allowed charclass (e.g. change re.sub r'[^a-z0-9_]' to allow '-') and
include hyphen in the strip call (strip('_-')) so we don't produce
leading/trailing hyphens, keep the existing path traversal checks (the '..' and
path separators) unchanged, and retain the numeric-prefix guard (prefix with
"app_") using the sanitized value so filesystem slugs remain safe; locate the
logic around variables app_id, normalized, sanitized inside
_validate_and_sanitize_app_id to apply these changes.
♻️ Duplicate comments (3)
plugin-repos/starlark-apps/tronbyte_repository.py (2)
91-114: Userequests.RequestException+logger.exceptionhere too.
Aligns with the session usage and improves diagnostics.♻️ Suggested update
- except Exception as e: - logger.error(f"Error fetching raw file {file_path}: {e}") - return None + except requests.RequestException as e: + logger.exception(f"Error fetching raw file {file_path}: {e}") + return None
274-279: Treat empty directories as a valid response.
if not datatreats[]as failure; use an explicitNonecheck.🐛 Suggested fix
- data = self._make_request(url) - if not data: + data = self._make_request(url) + if data is None: return False, None, "Failed to fetch app files"web_interface/blueprints/api_v3.py (1)
6374-6384: Add the plugin_manager guard for consistency.Most Starlark endpoints return a clear error if
plugin_managerisn’t initialized. This endpoint skips that guard and can throw a 500 during early startup.🔧 Guard for startup safety
def get_starlark_app_config(app_id): """Get configuration for a Starlark app.""" try: + if not api_v3.plugin_manager: + return jsonify({ + 'status': 'error', + 'message': 'Plugin manager not initialized', + 'pixlet_available': False + }), 500 + starlark_plugin = api_v3.plugin_manager.get_plugin('starlark-apps')
🧹 Nitpick comments (2)
plugin-repos/starlark-apps/tronbyte_repository.py (1)
54-89: Prefer specific request/JSON exceptions andlogger.exception.
This avoids swallowing unexpected errors and captures JSON decode failures cleanly.♻️ Suggested update
- except requests.Timeout: - logger.error(f"Request timeout: {url}") - return None - except requests.RequestException as e: - logger.error(f"Request error: {e}") - return None - except Exception as e: - logger.error(f"Unexpected error: {e}") - return None + except requests.Timeout: + logger.exception(f"Request timeout: {url}") + return None + except (requests.RequestException, ValueError) as e: + logger.exception(f"Request error: {e}") + return Noneplugin-repos/starlark-apps/frame_extractor.py (1)
34-103: Prefer specific PIL exceptions andlogger.exception.
This keeps unexpected failures visible without over-catching.♻️ Suggested update
-import logging -from typing import List, Tuple, Optional -from PIL import Image +import logging +from typing import List, Tuple, Optional +from PIL import Image, UnidentifiedImageError ... - except Exception as e: - error = f"Error loading WebP: {e}" - logger.error(error) - return False, None, error + except (UnidentifiedImageError, OSError) as e: + error = f"Error loading WebP: {e}" + logger.exception(error) + return False, None, error
| def validate_config(self) -> bool: | ||
| """ | ||
| Validate plugin configuration. | ||
| Ensures required configuration values are valid for Starlark apps. | ||
| Returns: | ||
| True if configuration is valid, False otherwise | ||
| """ | ||
| # Call parent validation first | ||
| if not super().validate_config(): | ||
| return False | ||
|
|
||
| # Validate magnify range (0-8) | ||
| if "magnify" in self.config: | ||
| magnify = self.config["magnify"] | ||
| if not isinstance(magnify, int) or magnify < 0 or magnify > 8: | ||
| self.logger.error("magnify must be an integer between 0 and 8") | ||
| return False | ||
|
|
||
| # Validate render_timeout | ||
| if "render_timeout" in self.config: | ||
| timeout = self.config["render_timeout"] | ||
| if not isinstance(timeout, (int, float)) or timeout < 5 or timeout > 120: | ||
| self.logger.error("render_timeout must be a number between 5 and 120") | ||
| return False | ||
|
|
||
| # Validate cache_ttl | ||
| if "cache_ttl" in self.config: | ||
| ttl = self.config["cache_ttl"] | ||
| if not isinstance(ttl, (int, float)) or ttl < 60 or ttl > 3600: | ||
| self.logger.error("cache_ttl must be a number between 60 and 3600") | ||
| return False | ||
|
|
||
| # Validate scale_method | ||
| if "scale_method" in self.config: | ||
| method = self.config["scale_method"] | ||
| valid_methods = ["nearest", "bilinear", "bicubic", "lanczos"] | ||
| if method not in valid_methods: | ||
| self.logger.error(f"scale_method must be one of: {', '.join(valid_methods)}") | ||
| return False | ||
|
|
||
| # Validate default_frame_delay | ||
| if "default_frame_delay" in self.config: | ||
| delay = self.config["default_frame_delay"] | ||
| if not isinstance(delay, (int, float)) or delay < 16 or delay > 1000: | ||
| self.logger.error("default_frame_delay must be a number between 16 and 1000") | ||
| return False | ||
|
|
||
| return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate max_frames in config to avoid runtime type errors.
_load_frames_from_cache passes this into optimize_frames; a non-int (e.g., string) will throw.
🐛 Suggested fix
# Validate default_frame_delay
if "default_frame_delay" in self.config:
delay = self.config["default_frame_delay"]
if not isinstance(delay, (int, float)) or delay < 16 or delay > 1000:
self.logger.error("default_frame_delay must be a number between 16 and 1000")
return False
+
+ # Validate max_frames
+ if "max_frames" in self.config:
+ max_frames = self.config["max_frames"]
+ if max_frames is not None and (not isinstance(max_frames, int) or max_frames < 0):
+ self.logger.error("max_frames must be a non-negative integer or null")
+ return FalseAs per coding guidelines, validate inputs and handle errors early.
🤖 Prompt for AI Agents
In `@plugin-repos/starlark-apps/manager.py` around lines 170 - 219, The config
validator misses checking "max_frames", which can be non-integer and later cause
a runtime error when passed from _load_frames_from_cache into optimize_frames;
add validation in validate_config to ensure "max_frames"
(self.config["max_frames"]) is an integer >= 1 (or an allowed upper bound if
desired), log an error via self.logger.error when invalid, and return False so
invalid configs are rejected before _load_frames_from_cache or optimize_frames
are called.
| def _validate_and_sanitize_app_id(app_id: Optional[str], fallback_source: Optional[str] = None) -> Tuple[Optional[str], Optional[str]]: | ||
| """ | ||
| Validate and sanitize app_id to a safe slug. | ||
| Args: | ||
| app_id: App ID to validate (can be None) | ||
| fallback_source: Source to generate app_id from if app_id is None/empty | ||
| Returns: | ||
| Tuple of (sanitized_app_id, error_message) | ||
| If error_message is not None, validation failed | ||
| """ | ||
| import re | ||
| import hashlib | ||
|
|
||
| # If app_id is not provided, generate from fallback_source | ||
| if not app_id and fallback_source: | ||
| app_id = fallback_source | ||
|
|
||
| if not app_id: | ||
| return None, "app_id is required" | ||
|
|
||
| # Check for path traversal attempts | ||
| if '..' in app_id or '/' in app_id or '\\' in app_id: | ||
| return None, "app_id contains invalid characters (path separators or '..')" | ||
|
|
||
| # Normalize to lowercase | ||
| normalized = app_id.lower() | ||
|
|
||
| # Replace invalid characters with underscore | ||
| # Allow only: lowercase letters, digits, underscore | ||
| sanitized = re.sub(r'[^a-z0-9_]', '_', normalized) | ||
|
|
||
| # Remove leading/trailing underscores | ||
| sanitized = sanitized.strip('_') | ||
|
|
||
| # Ensure it's not empty after sanitization | ||
| if not sanitized: | ||
| # Generate a safe fallback slug from hash | ||
| hash_slug = hashlib.sha256(app_id.encode()).hexdigest()[:12] | ||
| sanitized = f"app_{hash_slug}" | ||
|
|
||
| # Ensure it doesn't start with a number | ||
| if sanitized and sanitized[0].isdigit(): | ||
| sanitized = f"app_{sanitized}" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allow hyphens (or preserve original IDs) to avoid repository lookup mismatches.
_validate_and_sanitize_app_id collapses all non‑[a-z0-9_] characters to _, which can mutate repository IDs (e.g., foo-bar → foo_bar). Because the repository install path uses the sanitized ID, apps whose IDs contain - (or other safe characters) may fail to fetch or collide. Consider allowing - (safe for filenames) or preserving the original ID for repo lookups while using a sanitized slug only for local paths.
💡 Suggested adjustment to preserve hyphens
- sanitized = re.sub(r'[^a-z0-9_]', '_', normalized)
+ sanitized = re.sub(r'[^a-z0-9_-]', '_', normalized)Tronbyte starlark app ID naming convention (do IDs include hyphens?)
🤖 Prompt for AI Agents
In `@web_interface/blueprints/api_v3.py` around lines 6122 - 6167,
_validate_and_sanitize_app_id currently replaces hyphens with underscores which
alters repo IDs like "foo-bar"; update the sanitizer so hyphens are allowed and
preserved for repo/path lookups by adding '-' to the allowed charclass (e.g.
change re.sub r'[^a-z0-9_]' to allow '-') and include hyphen in the strip call
(strip('_-')) so we don't produce leading/trailing hyphens, keep the existing
path traversal checks (the '..' and path separators) unchanged, and retain the
numeric-prefix guard (prefix with "app_") using the sanitized value so
filesystem slugs remain safe; locate the logic around variables app_id,
normalized, sanitized inside _validate_and_sanitize_app_id to apply these
changes.
| # Generate from filename if not provided | ||
| filename_base = file.filename.replace('.star', '') if file.filename else None | ||
| app_id, app_id_error = _validate_and_sanitize_app_id(app_id_input, fallback_source=filename_base) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strip client path from uploaded filename before deriving app_id.
Some browsers provide a C:\fakepath\... style filename. That currently trips the path‑separator check and blocks upload when app_id isn’t explicitly provided. Use Path(...).stem to ensure a clean base name.
✅ Safer filename base
- filename_base = file.filename.replace('.star', '') if file.filename else None
+ filename_base = Path(file.filename).stem if file.filename else None🤖 Prompt for AI Agents
In `@web_interface/blueprints/api_v3.py` around lines 6254 - 6256, The uploaded
filename may include client-side paths like "C:\fakepath\..." which trips the
path-separator check; update the filename base extraction before calling
_validate_and_sanitize_app_id by using the pathlib Path stem to strip any
directory and extension (i.e., derive filename_base from
Path(file.filename).stem when file.filename exists), ensuring you still handle a
missing file.filename by falling back to None and then pass that filename_base
into _validate_and_sanitize_app_id.
- Add partial route handler for starlark-apps in pages_v3.py - Add Starlark Apps navigation tab in main menu (between Fonts and Logs) - Add content area with HTMX lazy loading for starlark-apps partial - Users can now access Starlark apps management directly from the web interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@web_interface/blueprints/pages_v3.py`:
- Around line 311-316: In _load_starlark_apps_partial(), replace the broad
Exception handler with specific Jinja2 template exception handling: import
TemplateNotFound, TemplateError, TemplateSyntaxError from jinja2 and import
current_app from flask (if not already), then catch those specific exceptions
when rendering 'v3/partials/starlark_apps.html', log the failure with context
via current_app.logger.error including the template name and exception, and
return a generic user-friendly error message with a 500 status (do not return
the raw exception string).
| def _load_starlark_apps_partial(): | ||
| """Load Starlark apps management partial""" | ||
| try: | ||
| return render_template('v3/partials/starlark_apps.html') | ||
| except Exception as e: | ||
| return f"Error: {str(e)}", 500 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "pages_v3.py" -type fRepository: ChuckBuilds/LEDMatrix
Length of output: 103
🏁 Script executed:
cat -n ./web_interface/blueprints/pages_v3.py | sed -n '305,320p'Repository: ChuckBuilds/LEDMatrix
Length of output: 639
🏁 Script executed:
head -30 ./web_interface/blueprints/pages_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 1171
🏁 Script executed:
grep -n "_load_.*_partial()" ./web_interface/blueprints/pages_v3.py | head -20Repository: ChuckBuilds/LEDMatrix
Length of output: 932
🏁 Script executed:
web_search
Flask render_template exception handling best practices jinja2 TemplateNotFound TemplateErrorRepository: ChuckBuilds/LEDMatrix
Length of output: 159
🌐 Web query:
Flask render_template exception handling Jinja2 TemplateNotFound TemplateError
💡 Result:
Short answer: catch Jinja2's exceptions (TemplateNotFound, TemplateError/TemplateSyntaxError) where render_template is called or register global error handlers; also inspect the template loader search path to debug missing templates. Example patterns:
-
Per-view try/except
from flask import render_template, abort
from jinja2 import TemplateNotFound, TemplateError, TemplateSyntaxError@app.route('/')
def index():
try:
return render_template('index.html')
except TemplateNotFound:
# fallback or 404
abort(404)
except (TemplateSyntaxError, TemplateError) as e:
app.logger.exception("Template error")
return "Template error", 500 -
Global error handlers
from jinja2 import TemplateNotFound@app.errorhandler(TemplateNotFound)
def handle_template_not_found(e):
app.logger.warning("Missing template: %s", e)
return render_template('errors/404.html'), 404@app.errorhandler(TemplateError)
def handle_template_error(e):
app.logger.exception("Template rendering failed")
return render_template('errors/500.html'), 500 -
Debugging tips
- Print the loader search paths to see where Flask/Jinja2 looks for templates: print(app.jinja_loader.searchpath). [1][2]
- Ensure templates live in a templates/ folder (or set template_folder when creating Flask) and watch for casing/typos, blueprint template subfolders, packaging/deployment omissions. [1][2][3]
References:
- StackOverflow: common causes & fixes for TemplateNotFound. [1]
- DigitalOcean Flask error-handling tutorial (errorhandler pattern + TemplateNotFound examples). [2]
- Jinja2/Flask troubleshooting summary (loader searchpath, folder structure, TemplateSyntaxError). [3]
🏁 Script executed:
grep -n "current_app" ./web_interface/blueprints/pages_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 47
🏁 Script executed:
grep -n "import.*log\|logger\|logging" ./web_interface/blueprints/pages_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 47
🏁 Script executed:
ls ./web_interface/blueprints/Repository: ChuckBuilds/LEDMatrix
Length of output: 98
🏁 Script executed:
grep -n "import.*log\|logger\|logging\|current_app" ./web_interface/blueprints/api_v3.py | head -20Repository: ChuckBuilds/LEDMatrix
Length of output: 1026
🏁 Script executed:
head -20 ./web_interface/blueprints/api_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 693
🏁 Script executed:
grep -n "structured logging\|context\|logging" ./web_interface/blueprints/pages_v3.py | head -10Repository: ChuckBuilds/LEDMatrix
Length of output: 47
🏁 Script executed:
python3 << 'EOF'
# Verify Jinja2 exception hierarchy
try:
from jinja2 import TemplateNotFound, TemplateError, TemplateSyntaxError, UndefinedError
print("✓ TemplateNotFound: available")
print("✓ TemplateError: available")
print("✓ TemplateSyntaxError: available")
print("✓ UndefinedError: available")
# Check inheritance
print("\nException hierarchy:")
print(f"TemplateSyntaxError bases: {TemplateSyntaxError.__bases__}")
print(f"TemplateNotFound bases: {TemplateNotFound.__bases__}")
print(f"TemplateError bases: {TemplateError.__bases__}")
except ImportError as e:
print(f"Import error: {e}")
EOFRepository: ChuckBuilds/LEDMatrix
Length of output: 151
🏁 Script executed:
grep -rn "logging.getLogger\|logger = " ./web_interface/ --include="*.py" | head -10Repository: ChuckBuilds/LEDMatrix
Length of output: 951
🏁 Script executed:
grep -n "logger\.\(info\|error\|exception\|warning\)" ./web_interface/blueprints/api_v3.py | head -15Repository: ChuckBuilds/LEDMatrix
Length of output: 1350
🏁 Script executed:
sed -n '1,50p' ./web_interface/blueprints/pages_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 2114
Avoid leaking internal errors and log template failures with specific exception handling.
Lines 315-316 catch a broad Exception and return the raw error string to the client. Catch template-specific exceptions (TemplateNotFound, TemplateError, TemplateSyntaxError) instead, log failures for remote debugging with context, and return a user-friendly message to the client.
🛠️ Proposed fix (function body)
def _load_starlark_apps_partial():
"""Load Starlark apps management partial"""
try:
return render_template('v3/partials/starlark_apps.html')
- except Exception as e:
- return f"Error: {str(e)}", 500
+ except (TemplateNotFound, TemplateError, TemplateSyntaxError):
+ logger.exception("[Starlark Apps] Failed to render partial")
+ return "Unable to load Starlark Apps UI. Please check server logs.", 500➕ Required imports
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
+import logging
+from jinja2 import TemplateNotFound, TemplateError, TemplateSyntaxError
+
+logger = logging.getLogger(__name__)🧰 Tools
🪛 Ruff (0.14.13)
315-315: Do not catch blind exception: Exception
(BLE001)
316-316: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🤖 Prompt for AI Agents
In `@web_interface/blueprints/pages_v3.py` around lines 311 - 316, In
_load_starlark_apps_partial(), replace the broad Exception handler with specific
Jinja2 template exception handling: import TemplateNotFound, TemplateError,
TemplateSyntaxError from jinja2 and import current_app from flask (if not
already), then catch those specific exceptions when rendering
'v3/partials/starlark_apps.html', log the failure with context via
current_app.logger.error including the template name and exception, and return a
generic user-friendly error message with a 500 status (do not return the raw
exception string).
- Fix JavaScript initialization to work with HTMX lazy loading - Add modes property to StarlarkAppsPlugin to expose installed apps as display modes - Integrate repository listeners into main initialization - Each installed Starlark app now appears as a display mode in schedule/rotation - Repository browser now loads correctly when tab is accessed via HTMX
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@plugin-repos/starlark-apps/manager.py`:
- Around line 159-163: The code logs display dimensions by directly accessing
display_manager.matrix.width/height which can fail in headless contexts; update
the log calls (e.g., the block around calculated_magnify and any other places
referencing display_manager.matrix.*) to use display_manager.width() and
display_manager.height() helper methods instead, and replace any direct matrix
property access with these methods throughout the file (also update the other
occurrences noted) so the plugin works without hardware present; ensure you
update the f-strings (logger.info calls) to call display_manager.width() and
display_manager.height().
In `@web_interface/static/v3/js/starlark_apps.js`:
- Around line 488-490: The loop that sets value for each schema entry uses
"config[key] || field.default || ''" which overwrites valid falsy values like
false or 0; update the assignment in the for (const [key, field] of
Object.entries(schema)) block so value uses a null/undefined check on
config[key] (e.g., use the nullish coalescing pattern or an explicit check) to
prefer config[key] when it is false/0, falling back to field.default only if
config[key] is null/undefined, and finally to '' if both are undefined; adjust
the variable assignment for value accordingly in the same block.
- Around line 35-63: The current global init guard
(window.starlarkAppsInitialized) prevents initStarlarkApps from running after
HTMX swaps if the script ran earlier; change init logic to first check for a
stable root DOM node (e.g., document.querySelector for the Starlark UI
container) and only set window.starlarkAppsInitialized when that element exists,
or make setupEventListeners/setupRepositoryListeners idempotent per-container so
initStarlarkApps can safely re-run; specifically, in initStarlarkApps validate
presence of the Starlark UI root before returning and move setting of
window.starlarkAppsInitialized to after
setupEventListeners/setupRepositoryListeners and
loadStarlarkStatus/loadStarlarkApps complete, or update those setup* functions
to attach listeners only if not already attached to the target elements.
♻️ Duplicate comments (1)
plugin-repos/starlark-apps/manager.py (1)
226-233: Validatemax_framesin config to avoid runtime errors.
_load_frames_from_cacheforwardsmax_framesintooptimize_frames; non‑ints will throw. Add validation alongside other config checks. As per coding guidelines, validate inputs and handle errors early.🐛 Proposed fix
# Validate default_frame_delay if "default_frame_delay" in self.config: delay = self.config["default_frame_delay"] if not isinstance(delay, (int, float)) or delay < 16 or delay > 1000: self.logger.error("default_frame_delay must be a number between 16 and 1000") return False + + # Validate max_frames + if "max_frames" in self.config: + max_frames = self.config["max_frames"] + if max_frames is not None and (not isinstance(max_frames, int) or max_frames < 0): + self.logger.error("max_frames must be a non-negative integer or null") + return False
| # Calculate optimal magnification based on display size | ||
| self.calculated_magnify = self._calculate_optimal_magnify() | ||
| if self.calculated_magnify > 1: | ||
| self.logger.info(f"Display size: {self.display_manager.matrix.width}x{self.display_manager.matrix.height}, " | ||
| f"recommended magnify: {self.calculated_magnify}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing hardware matrix by using display helpers.
Direct access to display_manager.matrix.* can raise when running without hardware. Prefer display_manager.width() / height() to keep the plugin usable in headless/dev contexts.
🐛 Proposed fix
- if self.calculated_magnify > 1:
- self.logger.info(f"Display size: {self.display_manager.matrix.width}x{self.display_manager.matrix.height}, "
- f"recommended magnify: {self.calculated_magnify}")
+ if self.calculated_magnify > 1:
+ display_width = self.display_manager.width()
+ display_height = self.display_manager.height()
+ self.logger.info(
+ f"Display size: {display_width}x{display_height}, "
+ f"recommended magnify: {self.calculated_magnify}"
+ )- display_width = self.display_manager.matrix.width
- display_height = self.display_manager.matrix.height
+ display_width = self.display_manager.width()
+ display_height = self.display_manager.height()- display_width = self.display_manager.matrix.width
- display_height = self.display_manager.matrix.height
+ display_width = self.display_manager.width()
+ display_height = self.display_manager.height()- width = self.display_manager.matrix.width
- height = self.display_manager.matrix.height
+ width = self.display_manager.width()
+ height = self.display_manager.height()Also applies to: 245-247, 285-287, 621-622
🤖 Prompt for AI Agents
In `@plugin-repos/starlark-apps/manager.py` around lines 159 - 163, The code logs
display dimensions by directly accessing display_manager.matrix.width/height
which can fail in headless contexts; update the log calls (e.g., the block
around calculated_magnify and any other places referencing
display_manager.matrix.*) to use display_manager.width() and
display_manager.height() helper methods instead, and replace any direct matrix
property access with these methods throughout the file (also update the other
occurrences noted) so the plugin works without hardware present; ensure you
update the f-strings (logger.info calls) to call display_manager.width() and
display_manager.height().
| for (const [key, field] of Object.entries(schema)) { | ||
| const value = config[key] || field.default || ''; | ||
| const type = field.type || 'string'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve falsy config values (false/0).
config[key] || field.default overwrites valid falsy values, which can flip booleans or drop numeric 0s. Use null/undefined checks instead.
🛠️ Suggested fix
- const value = config[key] || field.default || '';
+ const value =
+ config[key] !== undefined && config[key] !== null
+ ? config[key]
+ : (field.default !== undefined && field.default !== null ? field.default : '');🤖 Prompt for AI Agents
In `@web_interface/static/v3/js/starlark_apps.js` around lines 488 - 490, The loop
that sets value for each schema entry uses "config[key] || field.default || ''"
which overwrites valid falsy values like false or 0; update the assignment in
the for (const [key, field] of Object.entries(schema)) block so value uses a
null/undefined check on config[key] (e.g., use the nullish coalescing pattern or
an explicit check) to prefer config[key] when it is false/0, falling back to
field.default only if config[key] is null/undefined, and finally to '' if both
are undefined; adjust the variable assignment for value accordingly in the same
block.
- Add one-click Pixlet installation button in Starlark Apps interface - Create /api/v3/starlark/install-pixlet endpoint to execute download script server-side - Fix duplicate 'Starlark Apps' tabs by removing hardcoded system tab from base.html - Rename plugin from 'Starlark Apps Manager' to 'Starlark Apps' in manifest - Add special routing for starlark-apps plugin to load full custom interface - Button shows spinner during download, handles errors, auto-refreshes on success - Ensure Browse Repository and Install Pixlet buttons always appear Users can now install Pixlet with one click directly in the browser. No terminal access required. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@plugin-repos/starlark-apps/pixlet_renderer.py`:
- Around line 250-261: The logger currently prints the full Pixlet command
including "-c key=value" which may leak secrets; modify the logging around the
cmd construction so the actual cmd used remains unchanged but build a separate
redacted representation for logging (e.g., map each config pair in config/items
to "-c key=<REDACTED>" or log only the config keys) and call logger.debug with
that redacted_cmd instead of the real cmd; update the code paths that reference
cmd, config, value_str, and logger.debug in pixlet_renderer.py to create and use
this redacted logging string while keeping the real cmd list intact for
execution.
In `@scripts/download_pixlet.sh`:
- Around line 85-92: The mv/chmod calls use set -e and will abort the whole
script on failure; change the move and permission steps around the variables
extracted_binary and output_path so each operation is checked and handled
per-arch (e.g., if mv "$extracted_binary" "$output_path" fails, log an error
referencing extracted_binary/output_path, rm -rf "$temp_dir" to clean up, and
return 1 for that arch instead of letting set -e abort the entire run), and do
the same for chmod +x "$output_path" (check its exit status, log a clear
message, clean temp_dir and return 1). Ensure final cleanup rm -rf "$temp_dir"
still runs on both success and failure paths.
In `@web_interface/static/v3/js/starlark_apps.js`:
- Around line 571-582: The form parsing loop relying solely on
formData.entries() misses unchecked checkboxes because FormData omits them;
update the handler that builds config from formData (the code using formData,
config and event.target.elements) to also iterate over all checkbox inputs in
the form (e.g., event.target.querySelectorAll('input[type="checkbox"][name]'))
and set config[checkbox.name] = checkbox.checked for each one (overwriting or
filling missing keys) so unchecked boxes are explicitly included as false.
♻️ Duplicate comments (6)
web_interface/blueprints/api_v3.py (3)
6151-6156: Preserve hyphens in app_id to avoid repository mismatches.Current sanitization rewrites
-to_, which can alter valid Tronbyte IDs and break lookups or cause collisions. Allow hyphens and trim_-at the ends.♻️ Suggested adjustment
- sanitized = re.sub(r'[^a-z0-9_]', '_', normalized) + sanitized = re.sub(r'[^a-z0-9_-]', '_', normalized) @@ - sanitized = sanitized.strip('_') + sanitized = sanitized.strip('_-')
6253-6256: Use Path.stem to avoidC:\fakepath\...rejection when deriving app_id.Some browsers include client-side paths in
file.filename, which then fails your path-separator guard. UsePath(...).stemto normalize.♻️ Safer filename base
- filename_base = file.filename.replace('.star', '') if file.filename else None + filename_base = Path(file.filename).stem if file.filename else None
6374-6380: Add a plugin_manager guard in get_starlark_app_config.This endpoint calls
api_v3.plugin_manager.get_plugin(...)without checking initialization, which can surface a 500 during early startup.✅ Suggested guard
def get_starlark_app_config(app_id): """Get configuration for a Starlark app.""" try: + if not api_v3.plugin_manager: + return jsonify({ + 'status': 'error', + 'message': 'Plugin manager not initialized', + 'pixlet_available': False + }), 500 + starlark_plugin = api_v3.plugin_manager.get_plugin('starlark-apps')web_interface/blueprints/pages_v3.py (1)
311-316: Catch specific template exceptions and avoid leaking internal errors.This broad
Exceptionhandler was flagged in a previous review. The raw error string exposes internal details to clients. Use specific Jinja2 exceptions (TemplateNotFound,TemplateError,TemplateSyntaxError), add logging for remote debugging, and return a user-friendly message.web_interface/static/v3/js/starlark_apps.js (2)
48-62: Init guard may skip HTMX-loaded UI elements.The global
window.starlarkAppsInitializedflag is set even if UI elements don't exist yet. If the Starlark partial is loaded via HTMX after the script initializes,setupEventListeners()won't re-run and buttons likeupload-star-btnwill lack handlers. Gate initialization on a stable element's existence, or makesetupEventListeners()idempotent.
497-499: Preserve falsy config values (false,0,"").The
||operator overwrites valid falsy values. Ifconfig[key]isfalseor0, the form will incorrectly displayfield.defaultinstead. Use nullish coalescing or explicit checks.🛠️ Suggested fix
- const value = config[key] || field.default || ''; + const value = (config[key] !== undefined && config[key] !== null) + ? config[key] + : (field.default ?? '');
🧹 Nitpick comments (1)
web_interface/static/v3/js/starlark_apps.js (1)
219-227: Consider checkingresponse.okbefore parsing JSON.The code parses
response.json()without checking HTTP status. If the server returns a non-JSON error (e.g., 502 from a proxy), parsing will throw. While the API appears to return JSON consistently, adding a status check improves resilience.♻️ Optional improvement
const response = await fetch('/api/v3/starlark/apps'); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } const data = await response.json();
| # Add configuration parameters | ||
| if config: | ||
| for key, value in config.items(): | ||
| # Convert value to string for CLI | ||
| if isinstance(value, bool): | ||
| value_str = "true" if value else "false" | ||
| else: | ||
| value_str = str(value) | ||
| cmd.extend(["-c", f"{key}={value_str}"]) | ||
|
|
||
| logger.debug(f"Executing Pixlet: {' '.join(cmd)}") | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid logging full -c key=value args to prevent secret leakage.
Config values may include tokens/keys; logging the full command can leak secrets. Log a redacted command or only the config keys.
🔒 Proposed redaction
- logger.debug(f"Executing Pixlet: {' '.join(cmd)}")
+ log_cmd = cmd.copy()
+ for i, arg in enumerate(log_cmd):
+ if arg == "-c" and i + 1 < len(log_cmd):
+ key = log_cmd[i + 1].split("=", 1)[0]
+ log_cmd[i + 1] = f"{key}=<redacted>"
+ logger.debug("Executing Pixlet: %s", " ".join(log_cmd))🤖 Prompt for AI Agents
In `@plugin-repos/starlark-apps/pixlet_renderer.py` around lines 250 - 261, The
logger currently prints the full Pixlet command including "-c key=value" which
may leak secrets; modify the logging around the cmd construction so the actual
cmd used remains unchanged but build a separate redacted representation for
logging (e.g., map each config pair in config/items to "-c key=<REDACTED>" or
log only the config keys) and call logger.debug with that redacted_cmd instead
of the real cmd; update the code paths that reference cmd, config, value_str,
and logger.debug in pixlet_renderer.py to create and use this redacted logging
string while keeping the real cmd list intact for execution.
| # Move to final location | ||
| mv "$extracted_binary" "$output_path" | ||
|
|
||
| # Make executable | ||
| chmod +x "$output_path" | ||
|
|
||
| # Clean up | ||
| rm -rf "$temp_dir" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle move/chmod failures per‑arch to avoid set -e aborting the whole script.
mv or chmod failures will exit the script and skip the summary/other arches. Consider explicit checks with cleanup and return 1 to preserve per‑arch reporting.
🐛 Proposed fix
- # Move to final location
- mv "$extracted_binary" "$output_path"
-
- # Make executable
- chmod +x "$output_path"
-
- # Clean up
- rm -rf "$temp_dir"
+ # Move to final location
+ if ! mv "$extracted_binary" "$output_path"; then
+ echo "✗ Failed to move binary to $output_path"
+ rm -rf "$temp_dir"
+ return 1
+ fi
+
+ # Make executable
+ if ! chmod +x "$output_path"; then
+ echo "✗ Failed to chmod $output_path"
+ rm -rf "$temp_dir"
+ return 1
+ fi
+
+ # Clean up
+ rm -rf "$temp_dir"🤖 Prompt for AI Agents
In `@scripts/download_pixlet.sh` around lines 85 - 92, The mv/chmod calls use set
-e and will abort the whole script on failure; change the move and permission
steps around the variables extracted_binary and output_path so each operation is
checked and handled per-arch (e.g., if mv "$extracted_binary" "$output_path"
fails, log an error referencing extracted_binary/output_path, rm -rf "$temp_dir"
to clean up, and return 1 for that arch instead of letting set -e abort the
entire run), and do the same for chmod +x "$output_path" (check its exit status,
log a clear message, clean temp_dir and return 1). Ensure final cleanup rm -rf
"$temp_dir" still runs on both success and failure paths.
| const formData = new FormData(event.target); | ||
| const config = {}; | ||
|
|
||
| for (const [key, value] of formData.entries()) { | ||
| // Handle checkboxes | ||
| const input = event.target.elements[key]; | ||
| if (input && input.type === 'checkbox') { | ||
| config[key] = input.checked; | ||
| } else { | ||
| config[key] = value; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unchecked checkboxes won't be submitted.
HTML FormData excludes unchecked checkboxes entirely. If a user unchecks a previously-checked field, that key won't appear in formData.entries(), so the backend won't receive false for it. Iterate over all checkbox inputs in the form to capture unchecked values explicitly.
🐛 Suggested fix
const formData = new FormData(event.target);
const config = {};
+ // First, set all checkboxes to false (unchecked won't be in formData)
+ event.target.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
+ config[checkbox.name] = false;
+ });
+
for (const [key, value] of formData.entries()) {
// Handle checkboxes
const input = event.target.elements[key];
if (input && input.type === 'checkbox') {
- config[key] = input.checked;
+ config[key] = true; // If it's in formData, it's checked
} else {
config[key] = value;
}
}🤖 Prompt for AI Agents
In `@web_interface/static/v3/js/starlark_apps.js` around lines 571 - 582, The form
parsing loop relying solely on formData.entries() misses unchecked checkboxes
because FormData omits them; update the handler that builds config from formData
(the code using formData, config and event.target.elements) to also iterate over
all checkbox inputs in the form (e.g.,
event.target.querySelectorAll('input[type="checkbox"][name]')) and set
config[checkbox.name] = checkbox.checked for each one (overwriting or filling
missing keys) so unchecked boxes are explicitly included as false.
- Fix modals using inline style instead of conflicting hidden/flex classes - Change modals to use style.display='flex'/'none' for proper centering - Add distinct error state when Starlark Apps plugin is not active - Show helpful message in repository browser when API returns error - Improve error handling for repository browse failures The modal display was broken because Tailwind's 'hidden' class (display:none) conflicts with 'flex' class. Using inline style ensures proper modal centering. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Status endpoint now checks Pixlet availability directly when plugin is installed but not loaded (common during initial setup) - Repository browse endpoint no longer requires loaded plugin instance - This allows users to see the Install Pixlet button and browse the repository even before the plugin has been activated The plugin being installed vs loaded are different states: - Installed: plugin files exist and appear in plugins list - Loaded: plugin instance is running (requires enabled + service running) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use importlib to dynamically load TronbyteRepository from plugin-repos - Python cannot import from directories with hyphens using standard import - Created _get_tronbyte_repository_class() helper function - Fixes "no module named plugin_repos" error in repository browse Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@web_interface/blueprints/api_v3.py`:
- Around line 6081-6093: The list endpoint is returning full app configs
(app_instance.config) which may contain secrets; remove the 'config' field from
the dict appended to apps_list in the loop (the block that builds each app entry
using app_id, app_data and app_instance.get_render_interval(),
get_display_duration(), schema, last_render_time) so the /starlark/apps response
no longer includes app_instance.config; rely on the existing per-app detail
endpoint for any config retrieval instead.
♻️ Duplicate comments (3)
web_interface/blueprints/api_v3.py (3)
6156-6202: Preserve hyphens in app IDs to avoid repo mismatches.Sanitizing
-to_can change repository IDs (e.g.,foo-bar→foo_bar) and break lookups or create collisions.🛠️ Suggested adjustment
- sanitized = re.sub(r'[^a-z0-9_]', '_', normalized) + sanitized = re.sub(r'[^a-z0-9_-]', '_', normalized) @@ - sanitized = sanitized.strip('_') + sanitized = sanitized.strip('_-')
6241-6367: Strip client paths when derivingapp_idfrom filename.Browsers can submit
C:\fakepath\...; usingPath(...).stemavoids path separators causing unnecessary validation failures.✅ Safer filename base
- filename_base = file.filename.replace('.star', '') if file.filename else None + filename_base = Path(file.filename).stem if file.filename else None
6407-6435: Guard against uninitializedplugin_manager.This endpoint calls
api_v3.plugin_manager.get_plugin(...)without checking forNone, which can throw during startup. Add a guard consistent with the other Starlark routes.🛡️ Suggested guard
try: + if not api_v3.plugin_manager: + return jsonify({ + 'status': 'error', + 'message': 'Plugin manager not initialized', + 'pixlet_available': False + }), 500 + starlark_plugin = api_v3.plugin_manager.get_plugin('starlark-apps')
| for app_id, app_data in apps.items(): | ||
| app_instance = starlark_plugin.apps.get(app_id) | ||
| if app_instance: | ||
| apps_list.append({ | ||
| 'id': app_id, | ||
| 'name': app_data.get('name', app_id), | ||
| 'enabled': app_data.get('enabled', True), | ||
| 'has_frames': app_data.get('has_frames', False), | ||
| 'render_interval': app_instance.get_render_interval(), | ||
| 'display_duration': app_instance.get_display_duration(), | ||
| 'config': app_instance.config, | ||
| 'has_schema': app_instance.schema is not None, | ||
| 'last_render_time': app_instance.last_render_time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for schema files with x-secret markers
find . -type f -name "*schema*.json" -o -name "*config*.json" | head -20Repository: ChuckBuilds/LEDMatrix
Length of output: 256
🏁 Script executed:
#!/bin/bash
# Search for x-secret markers in config-related files
rg "x-secret" -g "*.json" -nRepository: ChuckBuilds/LEDMatrix
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Examine the specific endpoint code in api_v3.py
sed -n '6070,6100p' web_interface/blueprints/api_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 1284
🏁 Script executed:
#!/bin/bash
# Read the Starlark apps config schema
cat ./plugin-repos/starlark-apps/config_schema.jsonRepository: ChuckBuilds/LEDMatrix
Length of output: 2939
🏁 Script executed:
#!/bin/bash
# Look at the endpoint definition and authentication
sed -n '6050,6070p' web_interface/blueprints/api_v3.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 776
🏁 Script executed:
#!/bin/bash
# Search for any existing config redaction or filtering logic
rg "config" web_interface/blueprints/api_v3.py -A 3 -B 3 | grep -i "redact\|secret\|filter\|sanitize" -A 2 -B 2Repository: ChuckBuilds/LEDMatrix
Length of output: 13973
🏁 Script executed:
#!/bin/bash
# Check if endpoint requires authentication
rg "@.*auth\|@.*login_required\|@.*require" web_interface/blueprints/api_v3.py -B 5 | grep -A 5 "apps"Repository: ChuckBuilds/LEDMatrix
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Look for per-app schema definitions
find ./plugin-repos/starlark-apps -type f -name "*.json" -o -name "*.py" | xargs grep -l "schema" 2>/dev/null | head -10Repository: ChuckBuilds/LEDMatrix
Length of output: 252
🏁 Script executed:
#!/bin/bash
# Check how other endpoints handle config exposure (comparison)
rg "config.*jsonify\|jsonify.*config" web_interface/blueprints/api_v3.py -A 2 -B 2 | head -40Repository: ChuckBuilds/LEDMatrix
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Examine the Starlark apps manager to see if individual apps have config schemas
sed -n '1,100p' ./plugin-repos/starlark-apps/manager.pyRepository: ChuckBuilds/LEDMatrix
Length of output: 3288
🏁 Script executed:
#!/bin/bash
# Search for how individual app configs are loaded/stored
rg "app_instance.config" web_interface/blueprints/api_v3.py -B 5 -A 5Repository: ChuckBuilds/LEDMatrix
Length of output: 638
🏁 Script executed:
#!/bin/bash
# Check if the endpoint should be protected based on similar endpoints
rg "@api_v3.route.*starlark\|@api_v3.route.*plugin" web_interface/blueprints/api_v3.py | head -20Repository: ChuckBuilds/LEDMatrix
Length of output: 47
Avoid returning full app configs in the list endpoint.
Individual Starlark apps can define their own configuration schemas with secret fields (like API keys, tokens). The /starlark/apps endpoint returns raw config from each app instance without filtering. While current app configs may not contain secrets, Starlark apps are designed to integrate with external APIs and future apps could store credentials in their configs. Additionally, the endpoint is unauthenticated, so any caller can access all app configurations. Omit config from the list response and rely on the per-app endpoint for detailed configuration retrieval.
Suggested mitigation
- 'config': app_instance.config,🤖 Prompt for AI Agents
In `@web_interface/blueprints/api_v3.py` around lines 6081 - 6093, The list
endpoint is returning full app configs (app_instance.config) which may contain
secrets; remove the 'config' field from the dict appended to apps_list in the
loop (the block that builds each app entry using app_id, app_data and
app_instance.get_render_interval(), get_display_duration(), schema,
last_render_time) so the /starlark/apps response no longer includes
app_instance.config; rely on the existing per-app detail endpoint for any config
retrieval instead.
- Remove guard that prevented data reload on subsequent tab views - Event listeners only set up once, but data always loads - Add console logging to help debug initialization issues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
HTMX doesn't execute script tags in swapped content by default. Use inline script to dynamically load starlark_apps.js and call initStarlarkApps() when tab content is loaded via HTMX. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@web_interface/templates/v3/partials/starlark_apps.html`:
- Around line 64-76: The drop zone and hidden file input are inaccessible to
keyboard users; update the elements around upload-star-form so the div with
id="upload-drop-zone" is focusable (add tabindex="0"), has role="button" and
appropriate aria-label/aria-describedby pointing to
star-file-input/selected-file-name, and add keydown handlers to activate the
file chooser on Enter/Space; also make the <input id="star-file-input"
name="file"> visually hidden (use sr-only or your visually-hidden utility)
instead of display:none so it remains accessible and keep the existing click
handler on upload-drop-zone to call star-file-input.click() so both mouse and
keyboard open the file picker and selected-file-name is announced/updated for
screen readers.
- Around line 211-238: The template can append multiple starlark_apps.js tags
during rapid HTMX swaps because it only checks window.starlarkAppsScriptLoaded;
add a loading guard by introducing window.starlarkAppsScriptLoading: if true,
return early; when starting to append the script set
window.starlarkAppsScriptLoading = true; in the script.onload handler set
window.starlarkAppsScriptLoaded = true and window.starlarkAppsScriptLoading =
false and call initStarlarkApps as before; in script.onerror set
window.starlarkAppsScriptLoading = false and log the error so subsequent
attempts can proceed; keep references to window.starlarkAppsScriptLoaded,
window.starlarkAppsScriptLoading, initStarlarkApps and starlark_apps.js to
locate the changes.
🧹 Nitpick comments (1)
web_interface/templates/v3/partials/starlark_apps.html (1)
8-10: Consider announcing Pixlet status updates to assistive tech.
This banner is JS-updated; addingrole="status"andaria-live="polite"helps screen readers.♻️ Suggested tweak
- <div id="pixlet-status-banner" class="mb-6 p-4 rounded-lg border"> + <div id="pixlet-status-banner" class="mb-6 p-4 rounded-lg border" role="status" aria-live="polite">
| <form id="upload-star-form" enctype="multipart/form-data"> | ||
| <div class="space-y-4"> | ||
| <!-- File Upload --> | ||
| <div> | ||
| <label class="block text-sm font-medium text-gray-700 mb-2"> | ||
| .star File <span class="text-red-500">*</span> | ||
| </label> | ||
| <div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors cursor-pointer" id="upload-drop-zone"> | ||
| <input type="file" id="star-file-input" name="file" accept=".star" class="hidden" required> | ||
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i> | ||
| <p class="text-sm text-gray-600">Click to browse or drag & drop your .star file here</p> | ||
| <p id="selected-file-name" class="text-sm text-blue-600 mt-2 hidden"></p> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the file picker keyboard accessible.
The file input is hidden and the drop zone isn’t focusable, so keyboard-only users can’t select a file. That’s an accessibility blocker.
🧩 Suggested fix
- <div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors cursor-pointer" id="upload-drop-zone">
- <input type="file" id="star-file-input" name="file" accept=".star" class="hidden" required>
+ <label for="star-file-input" class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors cursor-pointer block" id="upload-drop-zone" role="button" tabindex="0">
+ <input type="file" id="star-file-input" name="file" accept=".star" class="sr-only" required>
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i>
<p class="text-sm text-gray-600">Click to browse or drag & drop your .star file here</p>
<p id="selected-file-name" class="text-sm text-blue-600 mt-2 hidden"></p>
- </div>
+ </label>(If sr-only isn’t available in your CSS stack, use your project’s visually-hidden utility.)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <form id="upload-star-form" enctype="multipart/form-data"> | |
| <div class="space-y-4"> | |
| <!-- File Upload --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2"> | |
| .star File <span class="text-red-500">*</span> | |
| </label> | |
| <div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors cursor-pointer" id="upload-drop-zone"> | |
| <input type="file" id="star-file-input" name="file" accept=".star" class="hidden" required> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i> | |
| <p class="text-sm text-gray-600">Click to browse or drag & drop your .star file here</p> | |
| <p id="selected-file-name" class="text-sm text-blue-600 mt-2 hidden"></p> | |
| </div> | |
| <form id="upload-star-form" enctype="multipart/form-data"> | |
| <div class="space-y-4"> | |
| <!-- File Upload --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2"> | |
| .star File <span class="text-red-500">*</span> | |
| </label> | |
| <label for="star-file-input" class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors cursor-pointer block" id="upload-drop-zone" role="button" tabindex="0"> | |
| <input type="file" id="star-file-input" name="file" accept=".star" class="sr-only" required> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i> | |
| <p class="text-sm text-gray-600">Click to browse or drag & drop your .star file here</p> | |
| <p id="selected-file-name" class="text-sm text-blue-600 mt-2 hidden"></p> | |
| </label> |
🤖 Prompt for AI Agents
In `@web_interface/templates/v3/partials/starlark_apps.html` around lines 64 - 76,
The drop zone and hidden file input are inaccessible to keyboard users; update
the elements around upload-star-form so the div with id="upload-drop-zone" is
focusable (add tabindex="0"), has role="button" and appropriate
aria-label/aria-describedby pointing to star-file-input/selected-file-name, and
add keydown handlers to activate the file chooser on Enter/Space; also make the
<input id="star-file-input" name="file"> visually hidden (use sr-only or your
visually-hidden utility) instead of display:none so it remains accessible and
keep the existing click handler on upload-drop-zone to call
star-file-input.click() so both mouse and keyboard open the file picker and
selected-file-name is announced/updated for screen readers.
| <!-- Load and initialize Starlark Apps JavaScript --> | ||
| <script> | ||
| (function() { | ||
| // Check if script already loaded | ||
| if (window.starlarkAppsScriptLoaded) { | ||
| console.log('[Starlark] Script already loaded, calling init directly'); | ||
| // Reset the init flag to allow re-initialization of data | ||
| window.starlarkAppsInitialized = false; | ||
| if (typeof window.initStarlarkApps === 'function') { | ||
| window.initStarlarkApps(); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| // Load the script dynamically | ||
| console.log('[Starlark] Loading starlark_apps.js dynamically'); | ||
| var script = document.createElement('script'); | ||
| script.src = '/static/v3/js/starlark_apps.js'; | ||
| script.onload = function() { | ||
| console.log('[Starlark] Script loaded successfully'); | ||
| window.starlarkAppsScriptLoaded = true; | ||
| }; | ||
| script.onerror = function() { | ||
| console.error('[Starlark] Failed to load starlark_apps.js'); | ||
| }; | ||
| document.head.appendChild(script); | ||
| })(); | ||
| </script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect init behavior and script guard flags.
rg -n "initStarlarkApps|starlarkAppsScriptLoaded|starlarkAppsInitialized|DOMContentLoaded" -C 2 web_interface/static/v3/js/starlark_apps.jsRepository: ChuckBuilds/LEDMatrix
Length of output: 938
Add loading guard to prevent concurrent script loads during HTMX swaps.
The starlark_apps.js file already self-initializes (it checks document.readyState and calls initStarlarkApps() on load), so the init behavior is safe. However, the template only checks window.starlarkAppsScriptLoaded, leaving a race window where rapid HTMX swaps could inject multiple script tags before the first load completes. Add window.starlarkAppsScriptLoading flag to guard concurrent appends:
Suggested update
- if (window.starlarkAppsScriptLoaded) {
+ if (window.starlarkAppsScriptLoaded || window.starlarkAppsScriptLoading) {
console.log('[Starlark] Script already loaded, calling init directly');
// Reset the init flag to allow re-initialization of data
window.starlarkAppsInitialized = false;
if (typeof window.initStarlarkApps === 'function') {
window.initStarlarkApps();
}
return;
}
// Load the script dynamically
console.log('[Starlark] Loading starlark_apps.js dynamically');
+ window.starlarkAppsScriptLoading = true;
var script = document.createElement('script');
script.src = '/static/v3/js/starlark_apps.js';
script.onload = function() {
console.log('[Starlark] Script loaded successfully');
window.starlarkAppsScriptLoaded = true;
+ window.starlarkAppsScriptLoading = false;
};
script.onerror = function() {
+ window.starlarkAppsScriptLoading = false;
console.error('[Starlark] Failed to load starlark_apps.js');
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <!-- Load and initialize Starlark Apps JavaScript --> | |
| <script> | |
| (function() { | |
| // Check if script already loaded | |
| if (window.starlarkAppsScriptLoaded) { | |
| console.log('[Starlark] Script already loaded, calling init directly'); | |
| // Reset the init flag to allow re-initialization of data | |
| window.starlarkAppsInitialized = false; | |
| if (typeof window.initStarlarkApps === 'function') { | |
| window.initStarlarkApps(); | |
| } | |
| return; | |
| } | |
| // Load the script dynamically | |
| console.log('[Starlark] Loading starlark_apps.js dynamically'); | |
| var script = document.createElement('script'); | |
| script.src = '/static/v3/js/starlark_apps.js'; | |
| script.onload = function() { | |
| console.log('[Starlark] Script loaded successfully'); | |
| window.starlarkAppsScriptLoaded = true; | |
| }; | |
| script.onerror = function() { | |
| console.error('[Starlark] Failed to load starlark_apps.js'); | |
| }; | |
| document.head.appendChild(script); | |
| })(); | |
| </script> | |
| <!-- Load and initialize Starlark Apps JavaScript --> | |
| <script> | |
| (function() { | |
| // Check if script already loaded | |
| if (window.starlarkAppsScriptLoaded || window.starlarkAppsScriptLoading) { | |
| console.log('[Starlark] Script already loaded, calling init directly'); | |
| // Reset the init flag to allow re-initialization of data | |
| window.starlarkAppsInitialized = false; | |
| if (typeof window.initStarlarkApps === 'function') { | |
| window.initStarlarkApps(); | |
| } | |
| return; | |
| } | |
| // Load the script dynamically | |
| console.log('[Starlark] Loading starlark_apps.js dynamically'); | |
| window.starlarkAppsScriptLoading = true; | |
| var script = document.createElement('script'); | |
| script.src = '/static/v3/js/starlark_apps.js'; | |
| script.onload = function() { | |
| console.log('[Starlark] Script loaded successfully'); | |
| window.starlarkAppsScriptLoaded = true; | |
| window.starlarkAppsScriptLoading = false; | |
| }; | |
| script.onerror = function() { | |
| window.starlarkAppsScriptLoading = false; | |
| console.error('[Starlark] Failed to load starlark_apps.js'); | |
| }; | |
| document.head.appendChild(script); | |
| })(); | |
| </script> |
🤖 Prompt for AI Agents
In `@web_interface/templates/v3/partials/starlark_apps.html` around lines 211 -
238, The template can append multiple starlark_apps.js tags during rapid HTMX
swaps because it only checks window.starlarkAppsScriptLoaded; add a loading
guard by introducing window.starlarkAppsScriptLoading: if true, return early;
when starting to append the script set window.starlarkAppsScriptLoading = true;
in the script.onload handler set window.starlarkAppsScriptLoaded = true and
window.starlarkAppsScriptLoading = false and call initStarlarkApps as before; in
script.onerror set window.starlarkAppsScriptLoading = false and log the error so
subsequent attempts can proceed; keep references to
window.starlarkAppsScriptLoaded, window.starlarkAppsScriptLoading,
initStarlarkApps and starlark_apps.js to locate the changes.
- Move initStarlarkApps definition before window assignment - Remove auto-init when script loaded dynamically (let HTML handle it) - Add try-catch wrapper around init for better error visibility - Add cache-busting query param to script src for development - Simplify onload handler to call init directly without setTimeout Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of returning 404, return an empty apps list with a message when the Starlark plugin isn't loaded (e.g., Pixlet not installed). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Try to enable the starlark-apps plugin automatically after Pixlet is successfully downloaded, avoiding the need for a manual restart. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ction - Update download script to auto-detect latest Pixlet version - Fix file naming convention (pixlet_v0.50.2_linux-arm64.tar.gz) - Fix API status endpoint to look in correct bin/pixlet/ directory - Detect architecture-specific binary (linux-arm64 for Pi) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…aling
Implements a complete Starlark Apps plugin system that seamlessly imports widgets from the Tronbyte repository with zero modification required. The system provides automatic scaling for any display size and full management capabilities through a web UI.
Features:
Plugin Components:
Web Interface:
Auto-Scaling System:
API Endpoints:
Documentation:
Files Added:
Files Modified:
Summary by CodeRabbit
New Features
Web UI & API
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.